/**
 * Title:       AgentPlatform.java
 * Copyright:   Copyright (c) 1996-2004 The Agent Factory Working Group. All rights reserved.
 * Licence:     This file is free software; you can redistribute it and/or modify
 *              it under the terms of the GNU Lesser General Public License as published by
 *              the Free Software Foundation; either version 2.1, or (at your option)
 *              any later version.
 *
 *              This file is distributed in the hope that it will be useful,
 *              but WITHOUT ANY WARRANTY; without even the implied warranty of
 *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *              GNU Lesser General Public License for more details.
 *
 *              You should have received a copy of the GNU Lesser General Public License
 *              along with Agent Factory; see the file COPYING.  If not, write to
 *              the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *              Boston, MA 02111-1307, USA.
 */
package com.agentfactory.platform;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.rmi.server.UID;
import java.util.*;

import com.agentfactory.platform.core.*;
import com.agentfactory.platform.util.PlatformConfigReader;
import com.agentfactory.platform.service.PlatformServiceManager;
import com.agentfactory.platform.service.SecurityModule;
import com.agentfactory.platform.util.Logger;
import com.agentfactory.service.ams.AgentManagementService;

/**
 * A simple Agent Platform which can create and run agents specified by
 * their agent design files. Agents are stored in a hashtable using the agent
 * names as keys. If the platform is running on an ARM architecture AWT is used.
 * Otherwise a swing is utilised.
 *
 * This class, like every other core platform class, should not reference any
 * plugin classes directly. All plugin classes should instead be loaded
 * dynamically, or through the use of platform description files.
 *
 * @author Terry Lowen
 * @author Rem Collier
 * @author Robert Ross, Universitat Bremen
 */
public class AgentPlatform extends Observable {
    private static final int CLASS_LOG_LEVEL = Logger.LEVEL_ERROR;
    
    private static final String UNKNOWN = "unknown";
    
    public static final String SHUTDOWN_MESSAGE = "shutdown-now";
    
    /***************************************************************************
     * Platform attributes
     **************************************************************************/
    private String name;
    private String platformDomain;
    private PlatformServiceManager platformServiceManager;
    private SecurityModule securityModule;
    private AgentContainer agentContainer;
    
    /**
     * The next attribute is used to assess whether or not the platform has
     * been run. Specifically, this is used to force the AMS to create an
     * account for the platform with the SuperDF. After the first time, the
     * agent registers itself with the superDF.
     */
    private boolean firstRun = false;
    
    /**
     * This attribute contains a unique id that is generated once by the agent
     * platform. The id can be obtained only the first time it is requested.
     * This is done in the AMSModule, where it is used to authenticate the ams
     * when it tries to do things to the underlying agent platform.
     */
    private String platformID;
    String amsDesign,sdfDesign;
    /**
     * A hashtable of interpreter plugins that were specified in the config file.
     */
    private Hashtable interpreters = null;
    private Hashtable agentGUIs = null;
    private ArrayList systemAgents;
    private ArrayList applicationAgents;
    
    private boolean debugStartup = false;
   
    /**
     * Agent Platfrom Constructor to be used when supplying a specific
     * platform configurtion file. If not operating on IPAQ creates a Registry
     * Viewer of agents on the platform
     *
     * @param inConfigFileName -
     *            A String Object. The name of the platform configuration file
     *            to be used. This filename should be a resource name which can
     *            be located via a class loader. e.g.
     *            myPackage/mySubPackage/my_file
     *
     */
    public AgentPlatform(String inConfigFileName) {
        Logger.enter("AgentPlatform("+inConfigFileName + ")", CLASS_LOG_LEVEL);
        
        // Start the basic services
        this.agentContainer = new AgentContainer(this);
            
        PlatformConfigReader pcr = null;
        try {
            // parse the platform configuration file.
            pcr = new PlatformConfigReader(inConfigFileName);
        } catch (IOException ie) {
            Logger.error("Failed to read platform configuration: " +
                    inConfigFileName,
                    CLASS_LOG_LEVEL);
        }
            
        if (pcr != null) {
            name = pcr.getName() + "." + pcr.getPlatformDomain();
            
            platformDomain = pcr.getPlatformDomain();
            interpreters = pcr.getInterpreters();
            securityModule = new SecurityModule(pcr.getSecurityPolicies());
            platformServiceManager = PlatformServiceManager.newInstance(pcr.getServices(), this);
            initialiseGUIs(pcr);
        }
        
        systemAgents = new ArrayList();
        applicationAgents = new ArrayList();
        Logger.exit("AgentPlatform()", CLASS_LOG_LEVEL);
    }
    
    public void addSystemAgent(Agent agent) {
        System.out.println("[AgentPlatform] Adding System Agent: " + agent.getName());
        systemAgents.add(agent);
    }
    
    public void removeSystemAgent(Agent agent) {
        systemAgents.remove(agent);
        if (systemAgents.isEmpty()) {
            // Pause for 1 second
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            
            // Now resume all the application agents that were created...
            AgentManagementService ams = (AgentManagementService) platformServiceManager.getService("fipa.std.service.ams", this);
            for (int i=0; i<applicationAgents.size(); i++) {
                ams.resumeAgent((String) applicationAgents.get(i));
            }
            Logger.detail("[AgentPlatform: " + getName() +
                    "] System Agent Initialization Completed",
                    this.CLASS_LOG_LEVEL);
        }
    }
    
    public void addApplicationAgent(String name) {
        applicationAgents.add(name);
    }
    
    private void initialiseGUIs(PlatformConfigReader pcr) {
        // Initialise each of the GUI plugins. Remember, it is up to the GUI
        // to register with the Agent Factory platform and other plugins.
        Vector guis = pcr.getPlatformGUIs();
        
        Iterator iter = guis.iterator();
        while (iter.hasNext()) {
            PlatformGUIDescriptor descriptor = (PlatformGUIDescriptor) iter.next();
            PlatformGUI theGui = null;
            
            // Use factory instantiation on the GUI component
            Class[] types = { AgentPlatform.class };
            Object[] params = { this };
            try {
                Class nextClass = Class.forName(descriptor.getClassName());
                Constructor constructor = nextClass.getConstructor(types);
                theGui = (PlatformGUI) constructor.newInstance(params);
                theGui.setParameters(descriptor.getParameters());
                theGui.start();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(
                "could not find the constructor for a GUI Plugin : "
                + descriptor.getClassName());
            }
            //}
        }
    }
    
    /**
     * Get any registered interpreter plugins.
     *
     * @return a hashtable of interpreters
     */
    public Hashtable getInterpreters() {
        return this.interpreters;
    }
    
    /**
     * Used to control access to the underlying Agent Management Services
     * of the Agent Platform. The method returns a unique ID the first time
     * it is invoked, and returns null thereafter.
     *
     * @return A unique ID that can be used to access platform services.
     */
    public String generatePlatformID() {
        if (platformID == null) {
            platformID = new UID().toString();
        } else {
            return null;
        }
        
        return platformID;
    }
    
    /**
     * Method which may be used to shutdown the agent platform. All agents are
     * expliclty killed, any viewers destroyed, and important objects set to
     * null.
     */
    public void destrory() {
        // Shutdown anything which might have its own thread
        // todo - RR - this should be moved out. The AMS shutdown should take
        // care of this kind of thing.
        platformServiceManager.shutdown();
        
        // Go through each of the agent descirptions calling for the destruction
        // of each agent.
        agentContainer.terminateAllAgents();
        
        // Set all the important variables to null (just being paranoid). It
        // will allow garbage collection to start before the sleep statement
        // below
        this.platformServiceManager = null;
        
        // Sleep for a while. This gives the child threads a while to shutdown
        // This way we can be sure that the platform is actually shtudown
        // by the time the method returns.
        try {
            System.out.print("........... ");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        System.out.println("finished.");
        this.notify(SHUTDOWN_MESSAGE);
        System.exit(0);
    }
    
    /**
     * Notify any observers that the Platform has changed in some way.
     *
     * This mehtod seems to be unused at present. TODO - RR - Find out the the
     * Platform viewwer does actually ask for events from the Platform.
     */
    public void notify(String message) {
        this.setChanged();
        this.notifyObservers(message);
    }
    
    /***************************************************************************
     * Getters and Setters
     **************************************************************************/
    
    public String getName() {
        return name;
    }
    
    /**
     * Set the name of the platform. This is used in the SetPlatformNameActuator
     */
    public void setName(String inName) {
        Logger.enter("AgentPlatform:setName(" + inName + ")", CLASS_LOG_LEVEL);
        if (firstRun) {
            Logger.detail("Setting Platform Name!", CLASS_LOG_LEVEL);
            String oldPlatformName = this.name;
            this.name = inName;
            
            // Update names of any platform specific agents
            int index = 0;
            String oldName = null, newName = null;
            AgentManagementService ams = (AgentManagementService) platformServiceManager.getService("fipa.std.service.ams", this);
            ArrayList names = agentContainer.getPlatformSpecificAgentNames();
            for (int i = 0; i < names.size(); i++) {
                oldName = (String) names.get(i);
                index = oldName.indexOf('@');
                if (index > -1) {
                    if (oldName.substring(index + 1).equals(oldPlatformName)) {
                        newName = oldName.substring(0, index) + "@" + inName;
                        ams.modify(oldName, newName);
                    }
                }
            }
            
            // Update any platform services
            firstRun = false;
        }
        Logger.exit("AgentPlatform:setName(" + inName + ")", CLASS_LOG_LEVEL);
    }
        
    /**
     * Get the Platform Domain. This is used in the
     * AFInfrastructureModulePerceptor (what ever that is).
     */
    public String getPlatformDomain() {
        return platformDomain;
    }
    
    /**
     * Get the Agent Container.
     */
    public AgentContainer getAgentContainer() {
        return agentContainer;
    }
    
    /**
     * Returns the AgentPlatform's MessageTransportManager instance.
     *
     * @return a MessageTransportManager instance
     */
    public PlatformServiceManager getPlatformServiceManager() {
        return platformServiceManager;
    }
    
    public SecurityModule getSecurityModule() {
        return securityModule;
    }
}


